#ifndef BARCODE_EAN13_BINARY_HPP_INCLUDED
#define BARCODE_EAN13_BINARY_HPP_INCLUDED

#include "detail/ean13_mask.hpp"

#include "ean13_types.hpp"
#include "except.hpp"

#include <boost/dynamic_bitset.hpp>

#include <algorithm>
#include <iterator>
#include <list>

namespace barcode
{

namespace detail
{

template < class ForwardIterator >
boost::int8_t evaluate_prefix( ForwardIterator begin_, ForwardIterator end_ )
{
	throw_if< invalid_code_length >( std::distance( begin_, end_ ) != detail::ean13_mask_length_ );

	for ( ean13::prefix_type i_ = 0; i_ < detail::ean13_mask_length_; ++i_ )
	{
		if ( std::equal( begin_, end_, detail::ean13_mask_[ i_ ] ) )
			return i_;
	}

	return -1;
}

}

struct ean13_binary
{
private:
	typedef boost::dynamic_bitset<> binary_type;
	binary_type _binary;

	typedef std::list< boost::uint8_t > bytes_type;
	bytes_type _bytes;

public:
	typedef binary_type::const_reference const_reference;

	template < typename ByteInputIterator >
	ean13_binary( ByteInputIterator begin_, ByteInputIterator end_ );

	explicit ean13_binary( const ean13& ean13_ );

	std::size_t visible_bits_count() const;

	const_reference visible_bit_at( std::size_t bit_index_ ) const;

	typedef bytes_type::const_iterator const_byte_iterator;
	const_byte_iterator data_begin() const;
	const_byte_iterator data_end() const;

private:
	void append_binary( boost::uint8_t binary_, boost::uint8_t bits_count_ );
	void append_data( boost::uint8_t binary_ );
};

template < typename ByteInputIterator >
ean13_binary::ean13_binary( ByteInputIterator begin_, ByteInputIterator end_ )
{
	append_binary( _ean13_left_byte, _ean13_left_bits );

	for( std::size_t left_index_ = 0
		; begin_ != end_ && left_index_ < _ean13_left_length
		; ++left_index_, ++begin_ )
	{
		append_data( *begin_ );
	}

	append_binary( _ean13_center_byte, _ean13_center_bits );

	for( std::size_t right_index_ = 0
		; begin_ != end_ && right_index_ < _ean13_right_length
		; ++right_index_, ++begin_ )
	{
		append_data( *begin_ );
	}

	append_binary( _ean13_right_byte, _ean13_right_bits );

	throw_if< invalid_code_length >( _bytes.size() != _ean13_left_length + _ean13_right_length );
}

ean13_binary::ean13_binary( const ean13& ean13_ )
{
	ean13::prefix_type prefix_ = ean13_.prefix();

	append_binary( _ean13_left_byte, _ean13_left_bits );

	for( ean13::const_iterator left_it_ = ean13_.lbegin()
		; left_it_ != ean13_.rbegin()
		; ++left_it_ )
	{
		append_data( detail::ean13_mask_[ prefix_ ][ left_it_ - ean13_.lbegin() ][ *left_it_ ] );
	}

	append_binary( _ean13_center_byte, _ean13_center_bits );

	for( ean13::const_iterator right_it_ = ean13_.rbegin()
		; right_it_ != ean13_.end()
		; ++right_it_ )
	{
		append_data( detail::r_[ *right_it_ ] );
	}

	append_binary( _ean13_right_byte, _ean13_right_bits );
}

std::size_t ean13_binary::visible_bits_count() const
{
	return _binary.size();
}

ean13_binary::const_reference
ean13_binary::visible_bit_at( std::size_t bit_index_ ) const
{
	return _binary[ bit_index_ ];
}

ean13_binary::const_byte_iterator
ean13_binary::data_begin() const
{
	return _bytes.begin();
}

ean13_binary::const_byte_iterator
ean13_binary::data_end() const
{
	return _bytes.end();
}

void ean13_binary::append_binary( boost::uint8_t binary_, boost::uint8_t bits_count_ )
{
	for ( boost::uint8_t mask_ = 1 << (bits_count_ - 1); mask_; mask_ >>= 1 )
		_binary.push_back( ( binary_ & mask_ ) != 0 );
}

void ean13_binary::append_data( boost::uint8_t byte_ )
{
	append_binary( byte_, _ean13_valuable_bits );
	_bytes.push_back( byte_ );
}

template < class InputIterator >
ean13 ean13_from_binary( InputIterator begin_, InputIterator end_ )
{
	typedef std::list< boost::uint8_t* > lrg_type;
	lrg_type lrg_;

	ean13 ean13_;

	std::size_t current_index_ = 0;

	for ( ean13::iterator output_ = ean13_.lbegin()
		; begin_ != end_
		; ++begin_, ++output_, ++current_index_ )
	{
		detail::code_digit_type code_digit_ = detail::ean13_digit( *begin_ );
		throw_if< not_a_digit >( code_digit_.second == -1 );

		lrg_.push_back( code_digit_.first );

		*output_ = code_digit_.second;
	}

	throw_if< invalid_code_length >( current_index_ != detail::ean13_mask_length_ );

	boost::int8_t prefix_ = detail::evaluate_prefix( lrg_.begin(), lrg_.end() );

	throw_if< invalid_code_length >( prefix_ == -1 );

	ean13_.prefix( prefix_ );

	return ean13_;
}


inline ean13 from_binary( const ean13_binary& ean13_binary_ )
{
	return ean13_from_binary( ean13_binary_.data_begin()
		, ean13_binary_.data_end() );
}

}

#endif //BARCODE_EAN13_BINARY_HPP_INCLUDED
